package cn.thornbird.orgsync.config;

import cn.thornbird.orgsync.entity.LoginLog;
import cn.thornbird.orgsync.entity.User;
import cn.thornbird.orgsync.model.UserData;
import cn.thornbird.orgsync.repository.LoginLogRepository;
import cn.thornbird.orgsync.repository.UserRepository;
import cn.thornbird.orgsync.util.IpUtil;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration
@AllArgsConstructor
@Slf4j
public class SecurityConfig {

    public static final int MAX_FAILED_LOGIN_ATTEMPTS = 3;

    private final UserRepository userRepository;

    private final LoginLogRepository loginLogRepository;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        // /swagger-ui/index.html
        String[] swaggerMatchers = new String[] {
                "/swagger-ui/**",
                "/swagger-resources/**",
                "/v3/api-docs"
        };
        String[] frontMatchers = new String[] {
                "/*.ico",
                "/*.json",
                "/*.txt",
                "/*.png",
                "/*.pdf",
                "/libs/**",
                "/css/**",
                "/js/**",
                "/images/**",
                "/index/static/**",
                "/public/attachments/**",
                "/public/temp/**"
        };
        String[] restrictedMatchers = new String[] {
                "/public/audios/**"
        };
        httpSecurity.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
                .and().formLogin().loginPage("/sign_in")
                .successHandler(new LoginSuccessHandler(userRepository, loginLogRepository))
                .failureHandler(new LoginFailureHandler(userRepository))
                .and().rememberMe()
                .and().logout().logoutSuccessUrl("/").invalidateHttpSession(true);
        httpSecurity.authorizeHttpRequests(authorize -> {
            try {
                authorize.antMatchers(swaggerMatchers).permitAll()
                        .antMatchers(frontMatchers).permitAll()
                        .antMatchers(restrictedMatchers).denyAll()
                        .mvcMatchers("/api/**").permitAll()
                        .mvcMatchers("/sign_in", "/sign_up", "/reset_password", "/logout").permitAll()
                        .anyRequest().authenticated();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }).httpBasic(withDefaults());
        return httpSecurity.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    static class LoginSuccessHandler implements AuthenticationSuccessHandler {

        private final UserRepository userRepository;

        private final LoginLogRepository loginLogRepository;

        public LoginSuccessHandler(UserRepository userRepository, LoginLogRepository loginLogRepository) {
            this.userRepository = userRepository;
            this.loginLogRepository = loginLogRepository;
        }

        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                            Authentication authentication) throws IOException, ServletException {
            String ip = IpUtil.getClientIpAddress(request, authentication);
            String sessionId = request.getSession() == null ? "" : request.getSession().getId();
            String userAgent = IpUtil.getUserAgentText(request);
            String region = IpUtil.getRegionInfo(ip);
            UserAgent ua = IpUtil.getUserAgent(request);
            User user = userRepository.findFirstById(((UserData) authentication.getPrincipal()).getId());
            if (user != null) {
                user.setLastLoginIp(ip);
                user.setLastLoginTime(new Date());
                user.setLocked(false);
                user.setFailedLoginAttempts((short) 0);
                userRepository.save(user);
                LoginLog loginLog = new LoginLog();
                loginLog.generateNewId();
                loginLog.setUserId(user.getId());
                loginLog.setUsername(user.getUsername().toLowerCase());
                loginLog.setSessionId(sessionId);
                loginLog.setIp(ip);
                loginLog.setUserAgent(userAgent.substring(0, Math.min(userAgent.length(), 256)));
                loginLog.setRegion(null);
                loginLog.setOs(null);
                loginLog.setBrowser(null);
                loginLog.setRegion(region);
                loginLog.setOs(ua.getOperatingSystem() == null ? null : ua.getOperatingSystem().toString());
                loginLog.setBrowser(ua.getBrowser() == null ? null : ua.getBrowser().toString());
                loginLogRepository.save(loginLog);
            }
            SavedRequest savedRequest = new HttpSessionRequestCache().getRequest(request, response);
            if (savedRequest != null) {
                response.sendRedirect(savedRequest.getRedirectUrl());
            } else {
                response.sendRedirect("/");
            }
        }

    }


    static class LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {

        private final UserRepository userRepository;

        public LoginFailureHandler(UserRepository userRepository) {
            super();
            setUseForward(false);
            this.userRepository = userRepository;
        }

        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                            AuthenticationException exception) throws IOException, ServletException {
            String username = request.getParameter("username");
            User user = userRepository.findFirstByUsernameIgnoreCase(username);
            if (user == null) {
                user = userRepository.findFirstByMobile(username);
            }
            if (user == null) {
                user = userRepository.findFirstByEmailIgnoreCase(username);
            }
            if (user != null) {
                if (user.getFailedLoginAttempts() == null) {
                    user.setFailedLoginAttempts((short) 1);
                } else {
                    user.setFailedLoginAttempts((short) (user.getFailedLoginAttempts() + 1));
                }
                if (user.getFailedLoginAttempts() >= MAX_FAILED_LOGIN_ATTEMPTS) {
                    user.setLocked(true);
                }
                userRepository.save(user);
            }
            saveException(request, exception);
            getRedirectStrategy().sendRedirect(request, response, "/sign_in?error");
        }

    }

}
